import java.net.*;
import java.nio.charset.CharsetDecoder;
import java.util.Arrays;
import java.util.Scanner;
import java.io.*;

/* Uses DataInputStream to read a blob of data by calling read() once each time it tries to read*/

public class ByteDataExperimentClient {
	
	private static PrintStream fileOut;
	private static int BUFFERSIZE = 1024;
	
	public static void main(String[] args) throws FileNotFoundException {
		if (args.length != 3) {
			printUsage();
			System.exit(-1);
		}
		
		String hostName = args[0];
		int port = Integer.parseInt(args[1]);
		int serverState = Integer.parseInt(args[2]);
		Scanner stdin = new Scanner(System.in);
		fileOut = new PrintStream(new File("ByteDataClientAgainstServerState" + serverState + ".txt"));
		
		// NOTE: only testing read behavior, so aId only stays at 0
		for (int aId = 0; aId < 1; aId++) {
			String prefix = "Client(ActionId=" + aId + ")";
			printlnToFileAndStdOut(prefix + " starting new action against server at stateId " + serverState + "...");

			// wait until server comes back up (if previously crashed/network down)
			if (serverState == 6 || serverState == 7) {
				printlnToFileAndStdOut(prefix + "Server is in Crash/Network down state");
				
				fileOut.print(prefix + " When server is ready to accept a new connection, " +
	    				"enter anything in the console and hit return: ");
	    		System.out.print(prefix + " When server is ready to accept a new connection, " +
	    				"enter anything in the console and hit return: ");
	    		// simply block the process until use entered something
	    		String stdinput = stdin.nextLine();
	    		printlnToFileAndStdOut(stdinput);
			}

			Socket socket;
			try {
				socket = new Socket(hostName, port);
			} catch (Exception e) {
				printlnToFileAndStdOut(prefix + " Socket constructor threw an exception");
				printStackTraceToFileAndStdErr(e);
				continue;
			}
			
			printlnToFileAndStdOut(prefix + " Call to Socket constructor successful. Hostname: " + hostName);

			PrintWriter out;
			DataInputStream in;
			
			try {
				out = new PrintWriter(socket.getOutputStream(), true);
				in = new DataInputStream(socket.getInputStream());
			} catch (Exception e) {
				printlnToFileAndStdOut(prefix + " PrintWriter/BufferedReader constructors threw an exception");
				printStackTraceToFileAndStdErr(e);
				continue;
			}
			
			printlnToFileAndStdOut(prefix + " Created PrintWriter/BufferedReader");
			
			byte[] inputData = null;
			String outputLine = null;
			
			// send client test payload
			printlnToFileAndStdOut(prefix + "Sending test payload to server...");
		    outputLine = "I am a client instance set to actionId " + aId;
		    out.println(outputLine);
			
			// get server test payload
//			try {
//				socket.setSoTimeout(1000);
//			} catch (SocketException e) {
//				printlnToFileAndStdOut(prefix + " Socket.setSoTimeout() threw an exception.");
//				printStackTraceToFileAndStdErr(e);
//				break;
//			}    		
			try {				
				inputData = new byte[BUFFERSIZE];
				int size = in.read(inputData);
				if (size == -1) {
					// Reach the end of the message
					break;
				}
				String inputDataStr = new String(Arrays.copyOfRange(inputData,0,size));
				printlnToFileAndStdOut(prefix + " Received Server Test Payload With Content: " + inputDataStr);				
			} catch (IOException e) {
				printlnToFileAndStdOut(prefix + " DataInputStream.read() threw an exception");
				printStackTraceToFileAndStdErr(e);
			}	
		    
//			catch (java.net.SocketTimeoutException e) {
//				printlnToFileAndStdOut(prefix
//						+ " socket timed out from read test payload stage. Stop reading test payload.");
//			} 
		    // wait for order to engage
		    if (serverState == 6 || serverState == 7) {
				printlnToFileAndStdOut(prefix + "When ready to perform action " + aId + 
						"enter anything in the console and hit return:");				
	    		// simply block the process until use entered something
	    		String stdinput = stdin.nextLine();
	    		printlnToFileAndStdOut(stdinput);
			}
		    
		    for (int i = 0; i < 3; i++) {
		    	printlnToFileAndStdOut(prefix + " performing action the " + (i+1) + "-th time...");
	    	
			    switch (aId) {
			    	case 0:
			    		//printlnToFileAndStdOut(prefix + " Read action.");
//				    		printlnToFileAndStdOut(prefix + " Closing socket.");
//				    		socket.close();
//				    		printlnToFileAndStdOut(prefix + " socket closed. Sleep.");
//				    		Thread.sleep(10000);
						try {
							socket.setSoTimeout(1000);
						} catch (SocketException e) {
							printlnToFileAndStdOut(prefix + " Socket.setSoTimeout() threw an exception.");
							printStackTraceToFileAndStdErr(e);
							break;
						}
			    		
						try {
							//while (true) {
								inputData = new byte[BUFFERSIZE];
								int size = in.read(inputData);
								if (size == -1) {
									printlnToFileAndStdOut("DataInputStream.read returned -1");
									break;
								}
								String inputDataStr = new String(Arrays.copyOfRange(inputData,0,size));
								printlnToFileAndStdOut(prefix + " Received Server Test Payload With Content: " + inputDataStr);
							//}					
						} catch (java.net.SocketTimeoutException e) {
							printlnToFileAndStdOut(prefix
									+ " Timed out trying to read from socket");
						} catch (IOException e) {
							printlnToFileAndStdOut(prefix
											+ " DataInputSream.read() threw an exception");
							printStackTraceToFileAndStdErr(e);
						}  		
			    		
			    		//inputLine = in.readLine();
			    		//printlnToFileAndStdOut(prefix + " Received Server Payload With Content: " + inputLine);
			    		//Thread.sleep(1000);
			    		break;
			    	case 1:
			    		printlnToFileAndStdOut(prefix + " Sending payload to server...");
		    			outputLine = prefix + " Dummy Payload From Client";
		    			out.println(outputLine);
		    			printlnToFileAndStdOut(prefix + " PrintWriter returned from call to println()");
		    			//Thread.sleep(1000);
		    			break;
			    	case 2:
			    		printlnToFileAndStdOut(prefix + " Closing socket.");
			    		
						try {
							socket.close();
						} catch (IOException e) {
							printStackTraceToFileAndStdErr(e);
						}
						
			    		//Thread.sleep(1000);
			    		break;
			    	case 3:
			    		printlnToFileAndStdOut(prefix + " Shutting down socket input stream.");
			    		
						try {
							socket.shutdownInput();
						} catch (IOException e) {
							printStackTraceToFileAndStdErr(e);
						}
						
			    		//Thread.sleep(1000);
			    		break;
			    	case 4:
			    		printlnToFileAndStdOut(prefix + " Shutting down socket output stream.");
			    		
						try {
							socket.shutdownOutput();
						} catch (IOException e) {
							printStackTraceToFileAndStdErr(e);
						}
			    		
			    		//Thread.sleep(1000);
			    		break;
			    	case 5:
			    		printlnToFileAndStdOut(prefix + " Shutting down socket input and output stream.");
			    		
						try {
				    		socket.shutdownInput();
							socket.shutdownOutput();
						} catch (IOException e) {
							// TODO Auto-generated catch block
							printStackTraceToFileAndStdErr(e);
						}
			    		
			    		//Thread.sleep(1000);
			    		break;
			    	case 6:
			    		printlnToFileAndStdOut(prefix + " Calling socket.isConnected(). Result = " +
			    				socket.isConnected());
			    		//Thread.sleep(1000);
			    		break;
			    	case 7:
			    		printlnToFileAndStdOut(prefix + " Calling socket.isBound(). Result = " +
			    				socket.isBound());
			    		//Thread.sleep(1000);
			    		break;
			    	default:
			    		printlnToFileAndStdOut(prefix + "unimplemented action id");
			    		break;
			    }
		    	
		    	try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					printlnToFileAndStdOut(prefix + " Received Interrupted Exception.");
					printStackTraceToFileAndStdErr(e);
					continue;
				}
		    }
		    
		    // if in action involving altering the socket's state, wait for user input before
		    // closing the socket for good
		    if (aId == 2 || aId == 3 || aId == 4 || aId == 5) {
				printlnToFileAndStdOut(prefix + "When ready to close the socket for action " + aId + 
						" for good, enter anything and hit return");				
	    		// simply block the process until use entered something
	    		String stdinput = stdin.nextLine();
	    		printlnToFileAndStdOut(stdinput);
			}
		    
		    try {
				socket.close();
			} catch (IOException e) {
				printlnToFileAndStdOut(prefix + " socket.close() threw exception when " +
						"trying to close the socket for this action");
				printStackTraceToFileAndStdErr(e);
			}
		    
		    // TODO: delete this 
//	    	try {
//				Thread.sleep(10000);
//			} catch (InterruptedException e) {
//				printlnToFileAndStdOut(prefix + " Received Interrupted Exception.");
//				printStackTraceToFileAndStdErr(e);
//				continue;
//			}
		    printlnToFileAndStdOut(prefix + " done with this action. Switching to the next action...");
		    printlnToFileAndStdOut("");
		}
		
		stdin.close();
		fileOut.close();
	}
	
	private static void printUsage() {
		System.out.println("Usage: java ExperimentClient [hostname] [port-number] [serverStateId]");
	}
	
	private static void printlnToFileAndStdOut(String str) {
		System.out.println(str);
		if (fileOut != null) {
			fileOut.println(str);
		}
	}
	
	private static void printStackTraceToFileAndStdErr(Exception e) {
		e.printStackTrace();		
		if (fileOut != null) {
			e.printStackTrace(fileOut);
		}
	}
}
